﻿using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Data;
using ModelProject;
using DataProject.Resources;


namespace DataProject
{
    public class Repository<TEntity> : IRepository<TEntity> where TEntity : class, IObjectWithChangeTracker
    {
        #region private properties

        private IQueryableUnitOfWork repositoryUnitOfWork;

        #endregion

        #region constructor

        public Repository(IQueryableUnitOfWork unitOfWork)
        {
            if (unitOfWork == null)
            {
                throw new ArgumentNullException("unitOfWork", string.Format(CultureInfo.InvariantCulture, Resources.Messages.ArgumentCannotBeNull, "unitOfWork"));
            }

            this.repositoryUnitOfWork = unitOfWork;
        }

        #endregion

        #region IRepository<TEntity> Members

        public IUnitOfWork UnitOfWork
        {
            get
            {
                return this.repositoryUnitOfWork;
            }
        }

        public virtual void Add(TEntity item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item", Messages.ArgumentCannotBeNull);
            }

            if (item.ChangeTracker != null && item.ChangeTracker.State == ObjectState.Added)
            {
                this.UnitOfWork.RegisterChanges<TEntity>(item);
            }
            else
            {
                throw new InvalidOperationException(Resources.Messages.ChangeTrackerIsNullOrStateIsNotAdded);
            }
        }

        public virtual void Add(IList<TEntity> items)
        {
            if (items == null)
            {
                throw new ArgumentNullException("items", Resources.Messages.ArgumentCannotBeNull);
            }

            foreach (var item in items)
            {
                Add(item);
            }
        }

        public virtual void Remove(TEntity item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item", Resources.Messages.ArgumentCannotBeNull);
            }

            IObjectSet<TEntity> objectSet = CreateSet();

            objectSet.Attach(item);

            objectSet.DeleteObject(item);
        }

        public virtual void Remove(IList<TEntity> items)
        {
            if (items == null)
            {
                throw new ArgumentNullException("items", Resources.Messages.ArgumentCannotBeNull);
            }

            foreach (var item in items)
            {
                Remove(item);
            }
        }

        public virtual void RegisterItem(TEntity item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item", Resources.Messages.ArgumentCannotBeNull);
            }

            this.CreateSet().Attach(item);
        }

        public virtual void Modify(TEntity item)
        {
            if (item == null)
            {
                throw new ArgumentNullException("item", Resources.Messages.ArgumentCannotBeNull);
            }

            if (item.ChangeTracker != null && ((item.ChangeTracker.State & ObjectState.Deleted) != ObjectState.Deleted))
            {
                item.MarkAsModified();
            }
           
            this.UnitOfWork.RegisterChanges(item);
        }
     
        public virtual IEnumerable<TEntity> GetAll()
        {
            return this.CreateSet().AsEnumerable();
        }

        public virtual IEnumerable<TEntity> GetPagedElements<S>(int pageIndex, int pageCount, Expression<Func<TEntity, S>> orderByExpression, bool ascending)
        {
            if (pageIndex < 0)
            {
                throw new ArgumentException(Resources.Messages.PageIndexLessThanZero, "pageIndex");
            }

            if (pageCount <= 0)
            {
                throw new ArgumentException(Resources.Messages.PageCountLessOrEqualZero, "pageCount");
            }

            if (orderByExpression == null)
            {
                throw new ArgumentNullException("orderByExpression", string.Format(Resources.Messages.ArgumentCannotBeNull, "orderByExpression"));
            }

            IObjectSet<TEntity> objectSet = CreateSet();

            return objectSet.Paginate(orderByExpression, pageIndex, pageCount, ascending);
        }

        public virtual IEnumerable<TEntity> GetElementsFilteredByExpression(Expression<Func<TEntity, bool>> filter)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", string.Format(Resources.Messages.ArgumentCannotBeNull, "filter"));
            }

            return CreateSet().Where(filter).ToList();
        }

        public virtual IEnumerable<TEntity> GetElementsFilteredByExpression<S>(Expression<Func<TEntity, bool>> filter, Expression<Func<TEntity, S>> orderByExpression, bool ascending)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", string.Format(Resources.Messages.ArgumentCannotBeNull, "filter"));
            }

            if (orderByExpression == null)
            {
                throw new ArgumentNullException("orderByExpression", string.Format(Resources.Messages.ArgumentCannotBeNull, "orderByExpression"));
            }

            IObjectSet<TEntity> objectSet = CreateSet();

            if (ascending)
            {
                return objectSet.Where(filter).OrderBy(orderByExpression).ToList();
            }

            return objectSet.Where(filter).OrderByDescending(orderByExpression).ToList();
        }

        public virtual IEnumerable<TEntity> GetElementsFilteredByExpression<S>(Expression<Func<TEntity, bool>> filter, int pageIndex, int pageCount, Expression<Func<TEntity, S>> orderByExpression, bool ascending)
        {
            if (filter == null)
            {
                throw new ArgumentNullException("filter", string.Format(Resources.Messages.ArgumentCannotBeNull, "filter"));
            }

            if (pageIndex < 0)
            {
                throw new ArgumentNullException("pageIndex", string.Format(Resources.Messages.ArgumentCannotBeLessThanZero, "pageIndex"));
            }

            if (pageCount <= 0)
            {
                throw new ArgumentNullException("pageCount", string.Format(Resources.Messages.ArgumentCannotBeLessOrEqualThanZero, "pageCount"));
            }

            if (orderByExpression == null)
            {
                throw new ArgumentNullException("orderByExpression", string.Format(Resources.Messages.ArgumentCannotBeNull, "orderByExpression"));
            }

            IObjectSet<TEntity> objectSet = CreateSet();

            if (ascending)
            {
                return objectSet.Where(filter)
                                .OrderBy(orderByExpression)
                                .Skip(pageIndex * pageCount)
                                .Take(pageCount)
                                .ToList();
            }

            return objectSet.Where(filter)
                            .OrderByDescending(orderByExpression)
                            .Skip(pageIndex * pageCount)
                            .Take(pageCount)
                            .ToList();
        }

        public void UndoChanges()
        {
            ObjectContext unitOfWork = this.UnitOfWork as ObjectContext;
            foreach (System.Data.Objects.ObjectStateEntry x in unitOfWork.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified))
            {
                if (x.EntityKey != null)
                {
                    unitOfWork.Detach(x.Entity);
                }
            }
        }

        #endregion

        #region private methods

        private IObjectSet<TEntity> CreateSet()
        {
            if (this.UnitOfWork != null)
            {
                IObjectSet<TEntity> objectSet = this.repositoryUnitOfWork.CreateSet<TEntity>();

                ObjectQuery<TEntity> query = objectSet as ObjectQuery<TEntity>;

                if (query != null)
                {
                    query.MergeOption = MergeOption.NoTracking;
                }

                return objectSet;
            }

            throw new InvalidOperationException(Resources.Messages.ContainerCannotBeNull);
        }

        #endregion
    }
}
